package com.xsir.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.JWTVerifier;
import com.xsir.entity.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;


/**
 * 工具类中有三个方法
 * - generate()     生成jwt
 * - decode()       解密jwt
 * - verify()       验证jwt
 *
 * @author xsir
 */
@Slf4j
public class JwtUtil {
    /**
     * jwt过期时间，默认10天
     */
    public static long ttlMillis = 10 * 24 * 60 * 60 * 1000;
    /**
     * 生成签名时所使用的秘钥
     */
    public static EncodedSecretKey base64EncodedSecretKey = new EncodedSecretKey("this is a secretKey");
    /**
     * 生成签名的时候所使用的加密算法
     */
    public static SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS512;
    
    /**
     * 不提供构造方法
     */
    private JwtUtil() {
    }
    
    /**
     * 这里就是产生jwt字符串的地方
     * jwt字符串包括三个部分
     *  1、header
     *      - 当前字符串的类型，一般都是“JWT”
     *      - 哪种算法加密，“HS256”或者其他的加密算法
     * 所以一般都是固定的，没有什么变化
     *  2、payload
     *      - iat：签发时间，也就是这个jwt什么时候生成的
     *      - jti：JWT的唯一标识
     *      - iss：签发人，一般都是username或者userId
     *      - exp：过期时间
     *
     * @param iss 签发人
     * @param claims   荷载部分，包含用户信息 例如可以添加用户名、用户ID、用户（加密前的）密码等信息
     */
    public static String generate(String iss, Map<String, Object> claims) {
        if (claims == null) {
            claims = new HashMap<>(0);
        }
        // 签发时间（iat）：荷载部分的标准字段之一
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        
        // 下面就是在为payload添加各种标准声明和私有声明了
        JwtBuilder builder = Jwts.builder()
                                 // 荷载部分的非标准字段/附加字段，一般写在标准的字段之前。
                                 .setClaims(claims)
                                 // JWT ID（jti）：荷载部分的标准字段之一，JWT 的唯一性标识，虽不强求，但尽量确保其唯一性。
                                 .setId(UUID.randomUUID().toString())
                                 // 签发时间（iat）：荷载部分的标准字段之一，代表这个 JWT 的生成时间。
                                 .setIssuedAt(now)
                                 // 签发人（iss）：荷载部分的标准字段之一，代表这个 JWT 的所有者。通常是 username、userid 这样具有用户代表性的内容。
                                 .setIssuer(iss)
                                 // 设置生成签名的算法和秘钥
                                 .signWith(signatureAlgorithm, base64EncodedSecretKey.value());
        
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            // 过期时间（exp）：荷载部分的标准字段之一，代表这个 JWT 的有效期。
            builder.setExpiration(exp);
        }
        
        return builder.compact();
    }
    
    /**
     * JWT Token 由 头部 荷载部 和 签名部 三部分组成。签名部分是由加密算法生成，无法反向解密。
     * 而 头部 和 荷载部分是由 Base64 编码算法生成，是可以反向反编码回原样的。
     * 这也是为什么不要在 JWT Token 中放敏感数据的原因。
     *
     * @param jwtToken 加密后的token
     * @return claims 返回荷载部分的键值对
     */
    public static Claims decode(String jwtToken) {
        // 得到 DefaultJwtParser
        return Jwts.parser()
                   // 设置签名的秘钥
                   .setSigningKey(base64EncodedSecretKey.value())
                   // 设置需要解析的 jwt
                   .parseClaimsJws(jwtToken)
                   .getBody();
    }
    
    /**
     * 校验 token
     * 在这里可以使用官方的校验，或，
     * 自定义校验规则，例如在 token 中携带密码，进行加密处理后和数据库中的加密密码比较。
     *
     * @param jwtToken 被校验的 jwt Token
     */
    public static boolean verify(String jwtToken) {
        Algorithm algorithm = null;
        switch (signatureAlgorithm) {
            case HS512:
                algorithm = Algorithm.HMAC512(Base64.decodeBase64(base64EncodedSecretKey.value()));
                break;
            case HS256:
                algorithm = Algorithm.HMAC256(Base64.decodeBase64(base64EncodedSecretKey.value()));
                break;
            default:
                throw new AlgorithmMismatchException("不支持该算法");
        }
        
        JWTVerifier verifier = JWT.require(algorithm).build();
        
        // 校验不通过会抛出异常
        // catch异常捕获顺序，先小后大
        try {
            verifier.verify(jwtToken);
        } catch (TokenExpiredException tee) {
            log.error("Token已过期！");
            return false;
        } catch (JWTVerificationException e) {
            log.error("无效的Token！");
            return false;
        }
        
        return true;
    }
    
    /**
     * 对密钥进行base64编码
     *
     * @param secretKey 密钥
     * @return 返回加密后的EncodedSecretKey对象
     */
    public static EncodedSecretKey encodedSecretKey(String secretKey) {
        return new EncodedSecretKey(secretKey);
    }
    
    /**
     * EncodedSecretKey类
     * 内部辅助类
     */
    private static class EncodedSecretKey {
        private String encodedSecretKey;
        
        EncodedSecretKey(String secretKey) {
            encodedSecretKey = Base64.encodeBase64String(secretKey.getBytes());
        }
        
        public String value() {
            return encodedSecretKey;
        }
    }
}